#include"work_thread.h"
#include<sys/wait.h>
#include<fcntl.h>

#define MAX_ARG 10  //客户端发送的命令最大不超过10字节

char * get_cmd(char buff[],char *myargv[])
{
    if(buff==NULL||myargv==NULL)
    {
        return NULL;
    }
    char *p=NULL;//使用p来记住当前字符串分割到哪里了
    int i=0;//代表给myargv的第几个下标进行赋值
    char *s=strtok_r(buff," ",&p);//s为当前进行分割的字段
    while(s!=NULL)
    {
        myargv[i++]=s;
        s=strtok_r(NULL," ",&p);
    }
    return myargv[0];
}


void send_file(int c,char *name)
{
    if(name==NULL)//命令没有文件名
    {
        send(c,"err",3,0);//因为客户端等着接收数据，客户端并不知道自己发送的命令不合法，因此需要提醒客户端。如果不告诉客户端，此时客户端执行recv就会阻塞
        return ;
    }
    //有文件名就需要服务器尝试打开文件，读取文件大小和内容
    int fd=open(name,O_RDONLY);
    if(fd==-1)//文件打开失败
    {
        send(c,"open file err",13,0);
        return ;
    }
    //文件打开成功需要给客户端回复“ok# size”
    int filesize=lseek(fd,0,SEEK_END);//得到文件大小 
    lseek(fd,0,SEEK_SET);//将文件偏移量移动到文件起始位置
    char buff[128]={0};
    sprintf(buff,"ok#%d",filesize);//组装发送给客户端的报文。将数据以某种格式存放在buff中
    send(c,buff,strlen(buff),0);

    memset(buff,0,128);
    //接收客户端是否需要下载的信息
    int n=recv(c,buff,127,0);//"ok","err"
    if(n<=0)//说明客户端关闭了
    {
        close(fd);//一定需要关闭
       // close(c);是否close(c)不影响，因为程序最终会退出while循环会关闭c
        return ;
    }
    if(strcmp(buff,"ok")!=0)
    {
        close(fd);
        return ;
    }
    //给客户端发送文件内容
    char data[1024]={0};
    int num=0;//每一次读取到的数据个数
    while((num=read(fd,data,1024))>0)//从fd上读取数据，读取到的数据存放到data中，预期读取1024个字节。read返回值为0说明内容发送完毕了
    {
        send(c,data,num,0);//将data中的数据发送给c，发送num个字节
    }

    close(fd);
    printf("down file finish\n");

}


void recv_file(int c,char*filename)
{
    if(filename==NULL)
    {
        printf("输入文件名字无效\n");
        return;
    }
    
    char buff[128]={0};
    int n=recv(c,buff,127,0);

    printf("接收数据成功\n");
    if(n<=0)
    {
        printf("err\n");
        return ;
    }
    if(strncmp(buff,"ok#",3)!=0)
    {
        send(c,"err",3,0);
        return;
    }
    printf("接收数据成功\n");
    int filesize=atoi(buff+3);//ok#0
     printf("接收到文件大小:%d\n",filesize);
    int fd=open(filename,O_WRONLY|O_CREAT,0600);
    if(filesize==0)
    {
        send(c,"err",3,0);
        close(fd);
        return ;
    }
    if(fd==-1)
    {
        send(c,"err",3,0);
        return ;
    }
    send(c,"ok",2,0);

    //循环接收数据
    int curr_size=0;//当前累加的数据总数目
    int num=0;
    char data[1024]={0};
    while(1)
    {
        num=recv(c,data,1024,0);
        if(num<=0)
        {
            printf("客户端关闭\n");
            break;
        }
       printf("接收数据\n");
        write(fd,data,num);
        curr_size+=num;

        if(curr_size>=filesize)
        {
            break;
        }

    }
    printf("上传完毕\n");
    close(fd);
}


void*work_thread(void*arg)//工作线程
{
    int c =(int)arg;
    if(c<0)
    {
        pthread_exit(NULL);
    }
   
   //接收客户端发送过来的命令，执行完再给客户端回复结果
    while(1)
    {
        char buff[128]={0};//存放客户端发送过来的命令
        int num=recv(c,buff,127,0);//从c上接收数据，存放到buff中，预计接收127个字节，标志位置为0
        if(num<=0)//小于0说明出错了，等于0说明客户端关闭了
        {
            break;
        }
       //服务器成功接收到命令，就需要对buff中的数据进行分割解析

        char *myargv[MAX_ARG]={0};
        char *cmd=get_cmd(buff,myargv);
        if(cmd==NULL)
        {
            send(c,"cmd err",7,0);
            continue;
        }

        if(strcmp(cmd,"get")==0)
        {
            //下载 send_file(c)
            send_file(c,myargv[1]);

        }
        else if(strcmp(cmd,"up")==0)
        {
            //上传 recv_file(c)
            recv_file(c,myargv[1]);
        }
        else
        {
            //fork()+execvp
            //首先定义管道
            int pipefd[2];//定义2个整形作为创建无名管道的参数  pipefd[0]代表读端，pipefd[1]代表写端
           if(pipe(pipefd)==-1)
            {
                send(c,"err",3,0);
                continue;
            }
           //管道文件创建成功创建子进程
           pid_t pid=fork();
           if(pid==-1)
           {
               send(c,"err",3,0);
               continue;
           }
           if(pid==0)//子进程创建成功
           {
                //如何让子进程把打印到屏幕的信息写入无名管道中

                dup2(pipefd[1],1);//pipefd[1] 是无名管道的写端；1 是子进程标准输出设备（屏幕）。这句的意思是把管道文件描述符pipefd[1]对应的文件复制一份，拷贝复制给了子进程文件描述符为1对应的文件。所以屏幕就没有了，文件描述符1对应的就是管道的写端，从此向屏幕打印数据就是向管道写入数据
                dup2(pipefd[1],2);
                
                close(pipefd[0]);//关闭子进程读端。此次关不关闭影响不大，因为子进程命令一执行瞬间就结束了，进程一旦结束所有的描述符都会被关闭

               //子进程执行execvp替换操作
                execvp(cmd,myargv);
                perror("not find cmd");//打印到标准错误输出上（屏幕）
               

                exit(0);
           }

           //父进程
           close(pipefd[1]);//父进程对管道执行读操作，不进行写操作
           wait(NULL);//父进程等待子进程关闭，防止出现僵尸进程

           //父进程读管道数据
           char read_pipe[1024]={"ok#"};//存放从管道中读取的数据,如果数据过多可以采用循环方式读取
           read(pipefd[0],read_pipe+3,1020);//从pipefd[0]上进行读数据，读取的数据存放在read_pipe+3后面，期望读取1020个字节
            //读到数据需要回复给客户端
            send(c,read_pipe,strlen(read_pipe),0);
            close(pipefd[0]);//关闭父进程管道读端。由于该操作在循环里重复进行，创建管道需要使用文件描述符，如果不关闭，父进程的文件表会迅速被占满

        }
    }
    close(c);
    printf("%d client close\n",c);

}


void thread_start(int c)//启动线程
{
    pthread_t id;
    pthread_create(&id,NULL,work_thread,(void*)c);
}
